Android Settings中账户图片冲突问题的分析

问题描述:在设置–添加账号菜单多出Exchange重复账号 总共3个exchange账号条目,且功能相同。概率性问题,恢复出厂设置之后,可能就会出现(1/8)且有时候还会出现两个相同的exchange账号。

1、问题分析:

1)/Settings/src/com/android/settings/accounts/ChooseAccountActivity.java

出现异常时,settings模块的log中 关于pref.type的信息如下:

1
2
3
4
5
6
7
8
Line 19710: 01-01 07:00:19.379  6703  6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.exchange hide=false
Line 19712: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.email hide=false
Line 19714: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google.android.gm.exchange hide=false
Line 19716: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google hide=false
Line 19718: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.email.imap hide=false
Line 19720: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google.android.gm.legacyimap hide=false
Line 19722: 01-01 07:00:19.379 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.google.android.gm.pop3 hide=false
Line 19724: 01-01 07:00:19.389 6703 6703 I ChooseAccountActivity: NHideAccountItem: account type =com.android.email.pop3 hide=false

可以看出并没有重复的账户类型,那么,当pref.type正常时,为什么会出现账户图标出错的问题,发现整个preference的图标,名称的获取是从下面这行代码中得到的,然后就从/Settings/src/com/android/settings/accounts/ChooseAccountActivity.java出发跟踪了一下这部分代码。

1
2
3
4
5
6
7
8
9
10
11
12
    mAuthDescs = AccountManager.get(this).getAuthenticatorTypesAsUser(mUserHandle.getIdentifier());

**2)/frameworks/base/core/java/android/accounts/AccountManager.java**

public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
try {
return mService.getAuthenticatorTypes(userId);
} catch (RemoteException e) {
// will never happen
throw new RuntimeException(e);
}
}

在这个方法中,查看了mService的类型,如下:

private final IAccountManager mService;

根据IPC机制,我们跳转到AccountManagerService这个类。

3)frameworks/base/services/core/java/com/android/server/accounts/AccountManagerService.java

在getAuthenticatorTypes()方法中,主要为下面这句:

return getAuthenticatorTypesInternal(userId);

然后跳转到getAuthenticatorTypesInternal()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
private AuthenticatorDescription[] getAuthenticatorTypesInternal(int userId) {
Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
authenticatorCollection = mAuthenticatorCache.getAllServices(userId);
AuthenticatorDescription[] types =
new AuthenticatorDescription[authenticatorCollection.size()];
int i = 0;
for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
: authenticatorCollection) {
types[i] = authenticator.type;
i++;
}
return types;
}

然后我们可以推断出,其资源的加载还在这行代码中:

authenticatorCollection = mAuthenticatorCache.getAllServices(userId);

又因为:

private final IAccountAuthenticatorCache mAuthenticatorCache;

跳转至AccountAuthenticatorCache类。

4)/frameworks/base/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java

然后发现这个类,并没有getAllServices()方法,因为其继承自 RegisteredServicesCache类,所有转去 RegisteredServicesCache类查看其getAllServices()方法。

5)/frameworks/base/core/java/android/content/pm/RegisteredServicesCache.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* @return a collection of {@link RegisteredServicesCache.ServiceInfo} objects for all
* registered authenticators.
*/
public Collection<ServiceInfo<V>> getAllServices(int userId) {
synchronized (mServicesLock) {
// Find user and lazily populate cache
final UserServices<V> user = findOrCreateUserLocked(userId);
if (user.services == null) {
generateServicesMap(null, userId);
}
return Collections.unmodifiableCollection(
new ArrayList<ServiceInfo<V>>(user.services.values()));
}
}

根据其方法解释,我们大体可以理解它的具体功能,然后我们进入generateServicesMap(null, userId)方法。

发现在其中有这样一句:

final List<ResolveInfo> resolveInfos = queryIntentServices(userId);

其实现为:

1
2
3
4
5
 protected List<ResolveInfo> queryIntentServices(int userId) {
final PackageManager pm = mContext.getPackageManager();
return pm.queryIntentServicesAsUser(
new Intent(mInterfaceName), PackageManager.GET_META_DATA, userId);
}

根据名称,我们不难猜出,它是一个根据特定intent,userid查询相关操作的service,并将该service的数据读出。

那么我们就得看一下这个mInterfaceName是什么呢?

搜索了一下,发现它是在RegisteredServicesCache类初始化的时候被赋值的,因为RegisteredServicesCache类在这个分析链中是被AccountAuthenticatorCache继承的,然后我们就得回到AccountAuthenticatorCache类了,看看它的初始化

6)/frameworks/base/services/core/java/com/android/server/accounts/AccountAuthenticatorCache.java

AccountAuthenticatorCache的初始化如下:

1
2
3
4
5
public AccountAuthenticatorCache(Context context) {
super(context, AccountManager.ACTION_AUTHENTICATOR_INTENT,
AccountManager.AUTHENTICATOR_META_DATA_NAME,
AccountManager.AUTHENTICATOR_ATTRIBUTES_NAME, sSerializer);
}

然后我们回到一开始的AccountManager类,发现在其中有这样几行代码:

1
2
3
4
5
public static final String ACTION_AUTHENTICATOR_INTENT =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_META_DATA_NAME =
"android.accounts.AccountAuthenticator";
public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";

然后结合new Intent(mInterfaceName) =new Intent(“android.accounts.AccountAuthenticator”)。

又到了展现曾工工具强大的地方了,我们搜索了一下
android.accounts.AccountAuthenticator,然后发现在/packages/apps/Email/AndroidManifest.xml中,注册了八个存在这样<action>的service

1
2
3
4
<intent-filter>
<action
android:name="android.accounts.AccountAuthenticator" />
</intent-filter>

然后看了看他们的数据。

1
2
3
4
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/authenticator_pop3"
/>

点开/packages/apps/Email/res/xml/authenticator_pop3.xml

1
2
3
4
5
6
7
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
android:accountType="@string/account_manager_type_pop3"
android:icon="@mipmap/ic_launcher_mail"
android:smallIcon="@drawable/ic_notification_mail_24dp"
android:label="@string/pop3_name"
android:accountPreferences="@xml/account_preferences"
/>

对比其图标,账户类型等信息,确实是Settings模块中account的图标和账户。

7)/frameworks/base/core/java/android/content/pm/RegisteredServicesCache.java

然后就接着回到了generateServicesMap()方法中的。

1
final List<ResolveInfo> resolveInfos = queryIntentServices(userId);

发现之后,就是这样的一个循环:

1
2
3
4
5
6
7
8
9
10
11
12
for (ResolveInfo resolveInfo : resolveInfos) {
try {
ServiceInfo<V> info = parseServiceInfo(resolveInfo);
if (info == null) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString());
continue;
}
serviceInfos.add(info);
} catch (XmlPullParserException|IOException e) {
Log.w(TAG, "Unable to load service info " + resolveInfo.toString(), e);
}
}

于是接着看parseServiceInfo()方法,发现在其中有这样一个关键方法;

1
2
V v = parseServiceAttributes(pm.getResourcesForApplication(si.applicationInfo),
si.packageName, attrs);

因为AccountAuthenticatorCache类中,有对这个方法进行重写,具体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public AuthenticatorDescription parseServiceAttributes(Resources res,
String packageName, AttributeSet attrs) {
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AccountAuthenticator);
try {
final String accountType =
sa.getString(com.android.internal.R.styleable.AccountAuthenticator_accountType);
final int labelId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_label, 0);
final int iconId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_icon, 0);
final int smallIconId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_smallIcon, 0);
final int prefId = sa.getResourceId(
com.android.internal.R.styleable.AccountAuthenticator_accountPreferences, 0);
final boolean customTokens = sa.getBoolean(
com.android.internal.R.styleable.AccountAuthenticator_customTokens, false);
if (TextUtils.isEmpty(accountType)) {
return null;
}
android.util.Log.d("AccountAuthenticatorCache", "accountType:" + accountType+",packageName:" + packageName+",labelId:" +labelId+",iconId:" +iconId+",smallIconId:" +smallIconId+",prefId:" + prefId+",customTokens:" +customTokens);

return new AuthenticatorDescription(accountType, packageName, labelId, iconId,
smallIconId, prefId, customTokens);
} finally {
sa.recycle();
}
}

其中需要解释的是这个数组:

1
2
3
4
5
6
7
8
9
10
11
public TypedArray obtainAttributes (AttributeSet set, int[] attrs)(说明此函数)
说明:返回一个由AttributeSet获得的一系列的基本的属性值,不需要用用一个主题或者/和样式资源执行样式。

参数:

set:现在检索的属性值;

attrs:制定的检索的属性值

public void recycle()
返回先前检索的数组,稍后再用。

那么具体的表现可以联想一下这两部分:

<1>
1)public static final String AUTHENTICATOR_ATTRIBUTES_NAME = "account-authenticator";

2) /packages/apps/Email/res/xml/authenticator_pop3.xml中的account-authenticator标签中间的内容。
<2>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<declare-styleable name="AccountAuthenticator">
<!-- The account type this authenticator handles. -->
<attr name="accountType" format="string"/>
<!-- The user-visible name of the authenticator. -->
<attr name="label"/>
<!-- The icon of the authenticator. -->
<attr name="icon"/>
<!-- Smaller icon of the authenticator. -->
<attr name="smallIcon" format="reference"/>
<!-- A preferences.xml file for authenticator-specific settings. -->
<attr name="accountPreferences" format="reference"/>
<!-- Account handles its own token storage and permissions.
Default to false
-->
<attr name="customTokens" format="boolean"/>
</declare-styleable>

分析到此,基本可以结束,由于没有全局去看,基本是按照一个链条在走,所以还有是有一些疑惑的地方,如果大家在看的时候有什么新的见解,可以告诉我一块研究研究。

2、总结:

1)AccountAuthenticatorCache是Android平台中账户验证服务(Account AuthenticatorService,AAS)的管理中心。而AAS则由应用程序通过在AndroidManifest.xml中输出符合指定要求的Service信息而来。

2)RegisteredServicesCache是一个模板类,专门用于管理系统中指定Service的信息收集和更新,而具体是哪些Service由RegisteredServicesCache构造时的参数指定。而AccountAuthenticatorCache从RegisteredServicesCache派生。专门负责关于账户部分的Service的信息收集和更新。

3)在修改了framework部分的代码,之后,想在超级服务器上进行本地验证,可以讲修改的模块单独编译,比如编译service模块,可以用:

1
source build.sh 项目名 services -j32

修改了framework/base下的文件,可以进入framework/base目录下使用:mm命令编译

编译完成之后,进入out/target/project/特定名称/system/framework/目录下,查看其时间,将所有编译影响到的文件都在手机中进行替换即可。